using System;
using System.Collections.Generic;
using System.IO;
using NFreeMarker.Template;
using Number = NFreeMarker.Template.Utility.Number;

namespace NFreeMarker.Core
{
    /**
     * A method that takes a parameter and evaluates it as a scalar,
     * then treats that scalar as template source code and returns a
     * transform model that evaluates the template in place.
     * The template inherits the configuration and environment of the executing
     * template. By default, its name will be equal to 
     * <tt>executingTemplate.getName() + "$anonymous_interpreted"</tt>. You can
     * specify another parameter to the method call in which case the
     * template name suffix is the specified id instead of "anonymous_interpreted".
     * @version $Id: Interpret.java,v 1.2 2005/06/16 18:13:56 ddekany Exp $
     * @author Attila Szegedi
     */
    internal class Interpret : BuiltIn
    {
        /**
         * Constructs a template on-the-fly and returns it embedded in a
         * {@link TemplateTransformModel}.
         * 
         * <p>The built-in has two arguments:
         * the arguments passed to the method. It can receive at
         * least one and at most two arguments, both must evaluate to a scalar. 
         * The first scalar is interpreted as a template source code and a template
         * is built from it. The second (optional) is used to give the generated
         * template a name.
         * 
         * @return a {@link TemplateTransformModel} that when executed inside
         * a <tt>&lt;transform></tt> block will process the generated template
         * just as if it had been <tt>&lt;transform></tt>-ed at that point.
         */
        protected override ITemplateModel _getAsTemplateModel(Environment env)
        {
            ITemplateModel model = Target.GetAsTemplateModel(env);
            Expression sourceExpr;
            string id = "anonymous_interpreted";
            if(model is ITemplateSequenceModel)
            {
                sourceExpr = ((Expression)new DynamicKeyName(Target, new NumberLiteral(new Number.IntNumber(0))).CopyLocationFrom(Target));
                if (((ITemplateSequenceModel)model).Size() > 1)
                {
                    id = ((Expression)new DynamicKeyName(Target, new NumberLiteral(new Number.IntNumber(1))).CopyLocationFrom(Target)).GetStringValue(env);
                }
            }
            else if (model is ITemplateScalarModel)
            {
                sourceExpr = Target;
            }
            else
            {
                throw InvalidTypeException(model, Target, env, "sequence or string");
            }
            string templateSource = sourceExpr.GetStringValue(env);
            Template.Template parentTemplate = env.GetTemplate();
            try
            {
                var template = new Template.Template(parentTemplate.GetName() + "$" + id, new StringReader(templateSource), parentTemplate.GetConfiguration());
                template.SetLocale(env.GetLocale());
                return new TemplateProcessorModel(template);
            }
            catch(IOException e)
            {
                throw new TemplateException("", e, env);
            }
        }

        private class TemplateProcessorModel : ITemplateTransformModel
        {
            private readonly Template.Template _template;
            
            internal TemplateProcessorModel(Template.Template template)
            {
                _template = template;
            }
            
            public TextWriter GetWriter(TextWriter output, IDictionary<string, ITemplateModel> args)
            {
                try
                {
                    Environment env = Environment.GetCurrentEnvironment();
                    env.Include(_template);
                }
                catch(TemplateModelException)
                {
                    throw;
                }
                catch(IOException)
                {
                    throw;
                }
                // TODO: catch(RuntimeException e)
                //{
                //    throw;
                //}
                catch(Exception e)
                {
                    throw new TemplateModelException(e);
                }

                return output;

                //NOTE: Was return new Writer(out)
                //{
                //    public void close()
                //    {
                //    }
                    
                //    public void flush() throws IOException
                //    {
                //        out.flush();
                //    }
                    
                //    public void write(char[] cbuf, int off, int len) throws IOException
                //    {
                //        out.write(cbuf, off, len);
                //    }
                //};
            }
        }
    }
}